iT邦幫忙

2025 iThome 鐵人賽

DAY 3
0

https://ithelp.ithome.com.tw/upload/images/20250917/20168201lr75iu9EfC.png

前言:Actions、Calculations 與 Data

在上篇文章有提到,FP 的重點是要管理因副作用而產生的程式碼複雜性,而要如何管理副作用造成的意外結果呢?
首先我們要先辨別程式碼中哪些部分會有副作用,才能進一步去管理它。那要如何辨別呢? 根據《簡約的軟體開發思維:用 Functional Programming 重構程式 - 以 Javascript 為例》書中所述,在 FP 的世界裡會將程式碼歸類為 3 類 :  

  • Actions (動作):具備副作用的程式碼,也就是不純的函數。
  • Calculations (運算):沒有副作用的純函數,也就是上篇所提的純函數(pure function)
  • Data (資料):不會變動的靜態資訊。

舉例來說,以下這些程式碼我們可以分成三類來看它 :

// 關於某人的資訊,屬於 Data
{
    "firstname": "Eric",
    "lastname": "Normand"
}

// * 寄送電子郵件,屬於 Actions
sendEmail(to, from, subject, body)

// 求出一串數字總和,屬於 Calculations
sum(numbers)

// * 將資料儲存到資料庫,系統其他部分就可看到資料,屬於 Actions
saveUserDB(user)

// 對函式輸入相同字串不論多少次,每次輸出結果永遠相同,屬於 Calculations
string_length(str)

// * 每次呼叫,傳回的時間都不一樣,屬於 Actions
getCurrentTime()

// 單純數字陣列,屬於 Data

其中,Actions 類型的程式碼特別受 FP 程式設計師關注,因為 Actions 類型的函式,其執行結果會受呼叫時間與次數影響。例如   sendEmail(to, from, subject, body),呼叫一次是寄一封信,呼叫兩次是寄兩封信,這兩者代表的意義不同,也會造成不同的結果。

判斷 Actions、Calculations 與 Data 的方式

要如何快速地區分程式碼屬於哪種類型呢?在看到一段程式時,我們只需要問一個關鍵問題:「這段程式碼的執行結果,會不會因為『執行的時間點』或『執行的次數』而有所不同?」

  • 如果答案是「會」,那它就是一個 Action。
  • 如果答案是「不會」,那它就是 Calculation 或 Data。

如果答案是「不會」,我們再問第二個問題:「它能被執行嗎?」

  • 如果可以,它就是 Calculation (例如 sum() 函式)。
  • 如果不行,它就是 Data (例如 [5, 6, 7] 陣列)。

https://ithelp.ithome.com.tw/upload/images/20250917/20168201m5f6VTr6wk.png
圖 1 判斷 Actions、Calculations 與 Data 的方式(資料來源: 自行繪製)

透過這簡單的分類方式,可讓我們辨識出哪些是需要特別注意的 Actions,哪些是比較能放心的 Calculation 和 Data,能幫助我們聚焦副作用的管理。

舉例:實際案例中的 Actions、Calculations 與 Data

假設我們在一個電商網站上購物,當使用者點擊「加入購物車」時,網站會更新購物車內容並重新計算總金額。這個過程可以拆解成以下幾個步驟 :

  1. 使用者點擊「加入購物車」按鈕:這是一個 UI 事件,每次點擊都代表一次新的互動。它的結果跟「何時」與「幾次」有關,所以它是 Action。
  2. 前端整理商品資訊:前端準備要傳送給後端的請求內容,例如 { itemId: 'A-123', quantity: 1 }。這段 JSON 內容本身只是靜態的資訊,它不會執行任何事。這是 Data。
  3. 前端傳送 API 請求給後端:這是一個網路請求,發送一次和發送兩次是不同的。這是 Action。
  4. 後端接收到請求:這是一個會受外部觸發的事件,並讀取請求內容。這是 Action。
  5. 後端根據商品 ID 查詢價格,並計算新的購物車總額:這個計算過程,只要輸入的商品和購物車內容相同,不論執行幾次,結果都應該一樣。這是一個 Calculation。
  6. 後端更新資料庫中的購物車狀態:這會修改伺服器端的狀態,是一個典型的副作用。這是 Action。
  7. 後端回傳更新後的購物車資訊給前端:這又是一次網路互動,將結果傳回。這是 Action。

透過這個購物流程的案例,我們可以看到一個看似單純的功能,其實是由多個 Actions、Calculations 與 Data 交織而成。為了更深入理解為何這樣區分很重要,讓我們先來看看這三者各自的特徵。

Actions、Calculations 與 Data 的特徵

稍微介紹這三種類型的特徵如下:

1. Actions (動作)

  • 定義:任何會受執行時間、執行次數、或以上兩者影響的程式碼都算 Actions。
  • 特性:Actions 是我們的程式與外部世界互動的橋樑,也是副作用的主要來源。今天寄出一封緊急 Email,跟下週再寄,結果完全不同;同樣的 Email 只寄一次,跟寄十次,意義也不一樣。這就是 Actions 的時間與次數依賴性。
  • 管理的對象:Actions 本身不是壞事,但它們產生的副作用是 bug 的主要來源,因此 FP 提供了許多專門的工具來確保 Actions 的安全性,這些也會在後續文章提到。

2. Calculations (運算)

  • 定義:利用輸入推導出輸出的過程,也就是我們在第一篇提到的「純函數」。
  • 特性:Calculations 的核心特色是可靠。只要輸入相同,無論何時、何地、呼叫幾次,它的輸出永遠相同。這使得它們非常容易測試且可以安全地重複使用。這個特性源自於「參照透明性 (Referential Transparency)」,意思是任何一個 Calculation(例如 2 + 3)都可以被它的運算結果(5)直接取代,而不會改變程式的行為。 
  • 與 Data 的差異:雖然 Calculations 和 Data 的結果都不受呼叫時機與次數影響,但最大的不同在於 Calculations 是可執行但不透明的。除非你實際執行它,否則你無法知道 sum([5, 6, 7]) 的結果是什麼。

關於 Calculations,也就是純函數(pure function),會在後面提到更多內容。

3. Data (資料)

  • 定義:與事件有關的事實記錄。
  • 特性:Data 是最單純的程式碼元件。它是被動且透明的,本身不可執行。你可以直接看到它的內容,理解它的意義,而不需要執行任何東西。正因為如此,Data 的複雜性遠低於可執行的程式碼。
  • 多元解讀方式:同樣的 Data 可以有多種解讀方式。例如,一張餐廳收據的 Data,對顧客來說是追蹤花費的依據,但對餐廳經理來說,卻可以用來分析熱門餐點與銷售趨勢。

了解了這三者的特徵後,我們就能進一步探討區分它們的價值是什麼。

區分 Actions、Calculations 與 Data 的重要性

為何程式寫得好好的,還要去區分程式碼屬於哪一類?因為這能幫助我們隔離複雜性。

區分 Actions、Calculations 和 Data 的好處,在現代軟體開發中尤其明顯,特別是當我們面對分散式系統 (distributed systems) 時。

FP 並不是什麼新潮的技術,它背後的數學基礎早已存在。但它之所以在近年來越來越受歡迎,很大程度上是因為網路的普及。現在的應用程式,大多需要讓多個程式(例如前端、後端、微服務)透過網路溝通。

這種溝通方式帶來了新的挑戰:訊息可能不會按照我們預期的順序抵達、同樣的訊息可能重複傳送、甚至根本沒有送達。這代表我們必須更謹慎地處理「事件在何時發生」以及「發生了幾次」這類問題。

這時,Actions、Calculations 和 Data 的分類就顯示出它的威力了:

  • Data 與 Calculations 因為其結果不受執行時機與次數影響,所以它們天生就對分散式系統中的混亂情況有「免疫力」。
  • Actions 則直接面對這些挑戰,因為它們的結果恰恰就依賴於執行時機與次數。

因此就浮現了一個解決方案:當我們的程式對執行時間與次數的依賴度越低,出錯的機率就越低。透過提高程式中 Data 與 Calculations 的佔比,並使用專門的工具來小心管理 Actions,我們的軟體就能更從容地應對分散式系統帶來的挑戰。因為我們要重點關注的是 Actions,當 Actions 範圍越小,我們要關注的就越少,畢竟人的注意力有限嘛,總不能全部的程式碼都要小心翼翼其副作用造成的結果,當注意力集中在少數 Actions,我們管理起來就會更容易。

Actions、Calculations 和 Data 的應用時機

了解了 Actions、Calculations 和 Data 的概念與重要性後,我們該如何在實際開發中應用這套思維模式呢?以下是幾個可應用的時機:

  • 思考與設計階段:在開始開發前,先用 Actions、Calculations 和 Data 的框架來分析需求。將一個功能拆解成「需要執行的動作」(Actions)、「核心的商業邏輯」(Calculations),以及過程中會用到的「靜態資訊」(Data)。可以在一開始就建立清晰的架構。
  • 撰寫程式碼時:在實作過程中,應盡力將三種類型明確區分。一個好的實踐是時時反問自己:「這段邏輯能否從 Action 轉化為 Calculation?」、「這個 Calculation 的結果是否可以變成固定的 Data?」每一次成功的轉化,都是在降低程式的複雜度。
  • 閱讀與維護程式碼時:當我們接手或審視既有程式碼時,可以用 Actions、Calculations 和 Data 的視角來快速定位複雜性的來源。特別注意程式碼中的 Actions,因為它們是副作用的集中地,也是最需要小心翼翼對待的部分。

小結

上篇我們理解了 FP 重點是管理因副作用而產生的程式碼複雜性,而要如何管理呢?第一步就是先辨識程式碼中需要被注意的部分,在 《簡約的軟體開發思維:用 Functional Programming 重構程式 - 以 Javascript 為例》 書中提到,可以將程式分成 Actions、Calculations 和 Data 三個分類,可用來審視我們的程式碼中需要被注意的部分。

另外也將這三類整理成一個簡單的表格如下:

類別 執行結果是否受呼叫影響? 是否可執行? 特性 範例
Actions 具備副作用,是複雜性的主要來源,屬於不純的函數。 sendEmail(to, from, subject, body)saveUserDB(user)getCurrentTime()
Calculations 純粹的運算,可靠且易於測試,也就是所謂的純函數(pure function) sum(numbers)string_length(str)
Data 靜態、透明的資訊,是最單純的。 [1, 10, 2, 45, 3, 98]{"firstname": "Eric", "lastname": "Normand"}

辨識出這三者,我們就能知道程式碼中哪些部分是穩定可靠的,哪些部分是需要我們特別小心管理的「危險區域」。


上一篇
[Day02] 什麼是 Functional Programming?
系列文
30 天的 Functional Programming 之旅3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言